#include <TrueText/AtlasTextBuilder.h>
#include <TrueText/AtlasText.h>
#include <TrueText/TextMetrics.h>
#include <TrueText/TextBounds.h>

#include "ftutils.h"

namespace TrueText {

struct AtlasTextBuilderPrivates
{
	AtlasText atlasText;
	std::wstring string;
	boost::shared_ptr<AtlasMetrics> metrics;
	TextBounds bounds;
	bool needsBuild;
	bool yUp;

	void Build();
	void Invalidate() { this->needsBuild = true; } 
	bool NeedsBuild() const { return needsBuild; }
};

void AtlasTextBuilder::SetAtlasMetrics(boost::shared_ptr<AtlasMetrics> value)
{
	this->privates->metrics = value;
	this->privates->Invalidate();
	this->privates->yUp = false;
}

void AtlasTextBuilder::SetString(const wchar_t* str)
{
	this->privates->string = str;
	this->privates->Invalidate();
}

void AtlasTextBuilder::SetYAxisDirection(bool up)
{
	privates->yUp = up;
}

void AtlasTextBuilderPrivates::Build()
{
	if (!this->metrics.get())
	{
		TT_FAIL_UNEXPECTED();
		return;
	}

	// 1. Layout text
	AtlasText result;
	const wchar_t* current = this->string.c_str();
	int xPos = 0;
	int yPos = this->metrics->LineHeight << 6;
	while (*current)
	{
		std::map<wchar_t, LetterMetrics>::const_iterator letter = this->metrics->Letters.find(*current);
		if (letter == this->metrics->Letters.end())
		{
			TT_FAIL_UNEXPECTED();
			continue;
		}

		AtlasTextEntry e;
		e.Area = letter->second.bounds;
		e.Area.Shift(this->metrics->Offset);
		TT_ASSERT(e.Area.left >= 0);
		TT_ASSERT(e.Area.top >= 0);
		e.At = Point(
			(xPos >> 6) + (letter->second.origin.x - letter->second.bounds.left),
			this->yUp 
				? (yPos >> 6) - (letter->second.bounds.bottom - letter->second.origin.y) - this->metrics->LineHeight
				: (yPos >> 6) - (letter->second.origin.y - letter->second.bounds.top));

		result.Entries.push_back(e);

		xPos += letter->second.advance;
		++current;
	}

	// 2. Normalize positions

	Rectangle sz(
		std::numeric_limits<int>::max(),
		std::numeric_limits<int>::max(),
		std::numeric_limits<int>::min(),
		std::numeric_limits<int>::min());

	for (std::vector<AtlasTextEntry>::const_iterator i = result.Entries.begin(); i != result.Entries.end(); ++i)
	{
		if (i->At.x < sz.left)
			sz.left = i->At.x;
		if (i->At.y < sz.top)
			sz.top = i->At.y;

		const int w = i->Area.right - i->Area.left + 1;
		const int h = i->Area.bottom - i->Area.top + 1;

		if (i->At.x + w > sz.right)
			sz.right = i->At.x + w;
		if (i->At.y + h > sz.bottom)
			sz.bottom = i->At.y + h;
	}

	this->bounds.SetDimensions(
		Dimensions(sz.right, sz.bottom));

	this->atlasText = result;
	this->needsBuild = false;
}

AtlasTextBuilder::AtlasTextBuilder()
	: privates(new AtlasTextBuilderPrivates())
{ 
	privates->needsBuild = true;
}

AtlasTextBuilder::~AtlasTextBuilder()
{ }

const AtlasText& AtlasTextBuilder::GetAtlasText()
{
	if (privates->NeedsBuild())
		privates->Build();

	return privates->atlasText;
}

const TextBounds& AtlasTextBuilder::GetBounds()
{
	if (privates->NeedsBuild())
		privates->Build();

	return privates->bounds;
}

} // namespace TrueText